﻿#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
# - http://www.zhengzhong.net/
# - http://blog.zhengzhong.net/
#
# View functions for my homepage webapp.
#
# Created on 2008-12-19.
# $Id: views.py 310 2008-12-30 13:28:29Z heavyzheng $
#

import os
import sys
import time
import logging
import urllib
import zipfile
import StringIO

from django.template import loader
from django.template import Context
from django.http import HttpRequest
from django.http import HttpResponse
from django.http import HttpResponseNotFound
from django.http import HttpResponseRedirect

from google.appengine.api import memcache

from zzheng.webapp.config import Config
from zzheng.webapp.models import MODELS_REVISION
from zzheng.webapp.models import MyFile
from zzheng.webapp import users

#---------------------------------------------------------------------------------------------------
# Common functions
#---------------------------------------------------------------------------------------------------

def _get_param(query_dict, name):
    value = query_dict.get(name, "").decode("utf8").strip()
    if len(value) == 0:
        return None
    else:
        return value


def _get_common_context_dict(request):
    user = users.get_current_user(request)
    if not user:
        log_url  = users.create_login_url(request.path)
        is_admin = False
    else:
        log_url  = users.create_logout_url(request.path)
        is_admin = ( user.email() in Config.ADMIN_EMAILS )
    return { "user_"     : user,
             "log_url_"  : log_url,
             "is_admin_" : is_admin,
             "url_"      : Config.URL_PREFIX,
             "static_"   : Config.STATIC_PREFIX, }

#---------------------------------------------------------------------------------------------------
# 404 not-found page
#---------------------------------------------------------------------------------------------------

def not_found(request):
    ctxt_dict = { "url" : request.path, }
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("not_found.html")
    return HttpResponseNotFound(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------
# Home page: about me and about Java
#---------------------------------------------------------------------------------------------------

def home(request):
    ctxt_dict = _get_common_context_dict(request)
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("home.html")
    return HttpResponse(tmpl.render(ctxt))

def about_java(request):
    ctxt_dict = _get_common_context_dict(request)
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("java.html")
    return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------
# Google App Engine server environment
#---------------------------------------------------------------------------------------------------

def webapp_environ(request):
    user = users.get_current_user(request)
    if not user or user.email() not in Config.ADMIN_EMAILS:
        ctxt_dict = { "error" : "You are not allowed to see this page.", }
    else:
        # os_environ: list of OS environment variables.
        os_environ = []
        for key_value in os.environ.items():
            os_environ.append("%s = %s" % key_value)
        
        # sys_path: list of system path.
        sys_path = sys.path
        
        # cookies: key/value pairs saved in cookies.
        cookies = []
        for key_value in request.COOKIES.items():
            cookies.append("%s = %s" % key_value)
        
        # session: key/value pairs saved in session.
        session = []
        try:
            session.append("session_key = %s" % request.session.session_key)
            for key_value in request.session.items():
                session.append("%s = %s" % (key_value[0], str(key_value[1])))
        except AttributeError:
            session.append("Failed to get session from HTTP request.")
        
        # memcache_stats: statistics of memcache.
        memcache_stats = []
        memcache_stats_dict = memcache.get_stats()
        for key_value in memcache_stats_dict.items():
            memcache_stats.append("%s = %s" % key_value)
        
        # Construct the context dict.
        ctxt_dict = { "os_environ"     : os_environ,
                      "sys_path"       : sys_path,
                      "cookies"        : cookies,
                      "session"        : session,
                      "memcache_stats" : memcache_stats, }
    
    # Render the environment to HttpResponse.
    ctxt_dict.update(_get_common_context_dict(request))
    ctxt = Context(ctxt_dict)
    tmpl = loader.get_template("environ.html")
    return HttpResponse(tmpl.render(ctxt))

#---------------------------------------------------------------------------------------------------
# File repository
#---------------------------------------------------------------------------------------------------

def _split_tags(tags_str):
    if tags_str is not None:
        tags = tags_str.lower().replace(",", " ").replace(";", " ").split()
    else:
        tags = []
    return tags


def _get_raw_file(id):
    "Returns a raw file by ID."
    
    try:
        file = MyFile.get_by_id(int(id))
        if not file:
            raise Exception("File '%s' cannot be found." % id)
        else:
            file.downloads += 1
            file.put()
            resp = HttpResponse(file.data, mimetype=file.mime_type)
            resp["Content-Disposition"] = "attachment; filename=%s" % file.name
            return resp
    except Exception, ex:
        error = "Failed to get raw file '%s': %s" % (id, str(ex))
        logging.error(error)
        params = { "error" : error, }
        return HttpResponseRedirect("%s/file/?%s" % (Config.URL_PREFIX, urllib.urlencode(params)))


def _upload_file(request):
    "Gets the uploaded file and saves it in the datastore."

    try:
        # Collect user inputs from request.
        desc = _get_param(request.POST, "desc")
        md5  = _get_param(request.POST, "md5" )
        tags = _split_tags(_get_param(request.POST, "tags"))
        
        # Get uploaded file from request.
        query_dict = request.POST.copy()
        query_dict.update(request.FILES)
        file_dict = query_dict.get("file", None)
        if not file_dict:
            raise Exception("Failed to get uploaded file from request.")
        
        # Collect file information.
        file_name = _get_param(file_dict, "filename")
        mime_type = _get_param(file_dict, "content-type")
        content   = file_dict.get("content")
        if not file_name or not mime_type or not content:
            raise Exception("The uploaded file's information is incomplete.")
        
        # Check for name conflict.
        if MyFile.gql("WHERE name=:file_name", file_name=file_name).get():
            file_name += time.strftime(".%Y%m%d_%H%M%S", time.localtime())
        if MyFile.gql("WHERE name=:file_name", file_name=file_name).get():
            raise Exception("Failed to make unique file name (tried '%s')." % file_name)
        
        # Create a new file and insert to datastore.
        user = users.get_current_user(request)
        file = MyFile( name=file_name,
                       desc=desc,
                       mime_type=mime_type,
                       data=content,
                       md5=md5,
                       owner=user.google_user(),
                       tags=tags,
                       status=MyFile.PUBLIC_STATUS )
        file.put()
        params = { "info" : "File '%s' uploaded successfully." % file_name, }
    
    except Exception, ex:
        error = "Failed to upload file: %s" % str(ex)
        logging.error(error)
        params = { "error" : error, }
    return params


def _edit_file(request):
    "Updates file information."
    
    id = _get_param(request.POST, "id")
    try:
        file = MyFile.get_by_id(int(id))
        if not file:
            raise Exception("Failed to find file '%s'." % id)
        # Check for name conflict.
        name = _get_param(request.POST, "name")
        if name != file.name and MyFile.gql("WHERE name=:name", name=name).get() is not None:
            raise Exception("File with the same name '%s' already exists." % name)
        # Update file info.
        file.name = name
        file.desc = _get_param(request.POST, "desc")
        file.md5  = _get_param(request.POST, "md5")
        file.tags = _split_tags(_get_param(request.POST, "tags"))
        file.put()
        params = { "info" : "File '%s' updated successfully." % file.name, }
    except Exception, ex:
        error = "Failed to update file '%s': %s" % (id, str(ex))
        logging.error(error)
        params = { "error" : error, }
    return params


def _delete_file(request):
    "Deletes a file from the datastore."
    
    id = _get_param(request.POST, "id")
    try:
        file = MyFile.get_by_id(int(id))
        if not file:
            raise Exception("File '%s' cannot be found." % id)
        file.delete()
        params = { "info" : "File '%s' deleted successfully." % id, }
    except Exception, ex:
        error = "Fail to delete file '%s': %s" % (id, str(ex))
        logging.error(error)
        params = { "error" : error, }
    return params


def file(request, id=None):

    # If the file ID is provided, return the raw file.
    if id and request.method == "GET":
        return _get_raw_file(id)
    
    if request.method == "GET":
    
        tag = _get_param(request.GET, "tag")
        if tag:
            query = MyFile.gql( "WHERE status=:status AND tags=:tag ORDER BY upload_date DESC",
                                status=MyFile.PUBLIC_STATUS,
                                tag=tag )
        else:
            query = MyFile.gql( "WHERE status=:status ORDER BY upload_date DESC",
                                status=MyFile.PUBLIC_STATUS )
        files = query.fetch(20, 0) # TODO: paginator.
        ctxt_dict = { "error" : _get_param(request.GET, "error"),
                      "info"  : _get_param(request.GET, "info"),
                      "files" : files,
                      "tag"   : tag, }
        ctxt_dict.update(_get_common_context_dict(request))
        ctxt = Context(ctxt_dict)
        tmpl = loader.get_template("file.html")
        return HttpResponse(tmpl.render(ctxt))
    
    elif request.method == "POST":
    
        user = users.get_current_user(request)
        if not user or user.email() not in Config.ADMIN_EMAILS:
            error = "Permission denied for POST request to URL '%s'." % request.path
            logging.error(error)
            params = { "error" : error, }
        else:
            action = _get_param(request.POST, "action")
            if action == "upload":
                params = _upload_file(request)
            elif action == "edit":
                params = _edit_file(request)
            elif action == "delete":
                params = _delete_file(request)
            else:
                error = "Unknown action '%s' on file." % action
                logging.error(error)
                params = { "error" : error, }
        return HttpResponseRedirect("%s/file/?%s" % (Config.URL_PREFIX, urllib.urlencode(params)))


#---------------------------------------------------------------------------------------------------
# Zipdoc viewer
#---------------------------------------------------------------------------------------------------

def get_zipdoc(request, path):

    tokens     = path.split("/")
    zip_name   = tokens[0] + ".zip"
    entry_path = "/".join(tokens[1:])
    file = MyFile.gql("WHERE name=:name", name=zip_name).get()
    if file is not None:
        try:
            str_file = StringIO.StringIO(file.data)
            zip_file = zipfile.ZipFile(str_file)
            return HttpResponse(zip_file.read(entry_path))
        except Exception, ex:
            logging.error("Fail to read '%s' from zip file '%s'." % (entry_path, zip_name))
            raise Http404("The file '%s' cannot be found." % path)
    else:
        raise Http404("The file '%s' cannot be found." % path)





